/* ----------
 * backend_status.c
 *	  后端状态报告基础设施。
 *
 * Copyright (c) 2001-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/activity/backend_status.c
 * ----------
 */
#include "postgres.h"

#include "access/xact.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "port/atomics.h"		/* for memory barriers */
#include "storage/ipc.h"
#include "storage/proc.h"		/* 为 MyProc */
#include "storage/sinvaladt.h"
#include "utils/ascii.h"
#include "utils/backend_status.h"
#include "utils/guc.h"			/* 为 application_name */
#include "utils/memutils.h"


/* ----------
 * 包括辅助的总后端数量
 *
 * 我们为每个可能的 BackendId 保留一个插槽，另外为每种可能的辅助进程类型保留一个插槽。 （该方案假设在任何时候都不会存在多个相同类型的辅助进程。）MaxBackends 还包括 autovacuum 工作线程和后台工作线程。
 * ----------
 */
#define NumBackendStatSlots (MaxBackends + NUM_AUXPROCTYPES)


/* ----------
 * GUC 参数
 * ----------
 */
bool		pgstat_track_activities = false;
int			pgstat_track_activity_query_size = 1024;


/* 暴露以便 backend_progress.c 可以访问 */
PgBackendStatus *MyBEEntry = NULL;


static PgBackendStatus *BackendStatusArray = NULL;
static char *BackendAppnameBuffer = NULL;
static char *BackendClientHostnameBuffer = NULL;
static char *BackendActivityBuffer = NULL;
static Size BackendActivityBufferSize = 0;
#ifdef USE_SSL
static PgBackendSSLStatus *BackendSslStatusBuffer = NULL;
#endif
#ifdef ENABLE_GSS
static PgBackendGSSStatus *BackendGssStatusBuffer = NULL;
#endif


/* 包括辅助的后端状态 */
static LocalPgBackendStatus *localBackendStatusTable = NULL;

/* 包括辅助的总后端数量 */
static int	localNumBackends = 0;

static MemoryContext backendStatusSnapContext;


static void fc_pgstat_beshutdown_hook(int fc_code, Datum fc_arg);
static void fc_pgstat_read_current_status(void);
static void fc_pgstat_setup_backend_status_context(void);


/*
 * 报告 CreateSharedBackendStatus 所需的共享内存空间。
 */
Size BackendStatusShmemSize(void)
{
	Size		fc_size;

	/* BackendStatusArray: */
	fc_size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots);
	/* BackendAppnameBuffer: */
	fc_size = add_size(fc_size,
					mul_size(NAMEDATALEN, NumBackendStatSlots));
	/* BackendClientHostnameBuffer: */
	fc_size = add_size(fc_size,
					mul_size(NAMEDATALEN, NumBackendStatSlots));
	/* BackendActivityBuffer: */
	fc_size = add_size(fc_size,
					mul_size(pgstat_track_activity_query_size, NumBackendStatSlots));
#ifdef USE_SSL
	/* BackendSslStatusBuffer: */
	fc_size = add_size(fc_size,
					mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots));
#endif
#ifdef ENABLE_GSS
	/* BackendGssStatusBuffer: */
	fc_size = add_size(fc_size,
					mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots));
#endif
	return fc_size;
}

/*
 * 在 postmaster 启动期间初始化共享状态数组和几个字符串缓冲区。
 */
void CreateSharedBackendStatus(void)
{
	Size		fc_size;
	bool		fc_found;
	int			fc_i;
	char	   *fc_buffer;

	/* 创建或附加到共享数组 */
	fc_size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots);
	BackendStatusArray = (PgBackendStatus *)
		ShmemInitStruct("Backend Status Array", fc_size, &fc_found);

	if (!fc_found)
	{
		/*
		 * 我们是第一个 - 初始化。
		 */
		MemSet(BackendStatusArray, 0, fc_size);
	}

	/* 创建或附加到共享应用名称缓冲区 */
	fc_size = mul_size(NAMEDATALEN, NumBackendStatSlots);
	BackendAppnameBuffer = (char *)
		ShmemInitStruct("Backend Application Name Buffer", fc_size, &fc_found);

	if (!fc_found)
	{
		MemSet(BackendAppnameBuffer, 0, fc_size);

		/* 初始化 st_appname 指针。 */
		fc_buffer = BackendAppnameBuffer;
		for (fc_i = 0; fc_i < NumBackendStatSlots; fc_i++)
		{
			BackendStatusArray[fc_i].st_appname = fc_buffer;
			fc_buffer += NAMEDATALEN;
		}
	}

	/* 创建或附加到共享客户端主机名缓冲区 */
	fc_size = mul_size(NAMEDATALEN, NumBackendStatSlots);
	BackendClientHostnameBuffer = (char *)
		ShmemInitStruct("Backend Client Host Name Buffer", fc_size, &fc_found);

	if (!fc_found)
	{
		MemSet(BackendClientHostnameBuffer, 0, fc_size);

		/* 初始化 st_clienthostname 指针。 */
		fc_buffer = BackendClientHostnameBuffer;
		for (fc_i = 0; fc_i < NumBackendStatSlots; fc_i++)
		{
			BackendStatusArray[fc_i].st_clienthostname = fc_buffer;
			fc_buffer += NAMEDATALEN;
		}
	}

	/* 创建或附加到共享活动缓冲区 */
	BackendActivityBufferSize = mul_size(pgstat_track_activity_query_size,
										 NumBackendStatSlots);
	BackendActivityBuffer = (char *)
		ShmemInitStruct("Backend Activity Buffer",
						BackendActivityBufferSize,
						&fc_found);

	if (!fc_found)
	{
		MemSet(BackendActivityBuffer, 0, BackendActivityBufferSize);

		/* 初始化 st_activity 指针。 */
		fc_buffer = BackendActivityBuffer;
		for (fc_i = 0; fc_i < NumBackendStatSlots; fc_i++)
		{
			BackendStatusArray[fc_i].st_activity_raw = fc_buffer;
			fc_buffer += pgstat_track_activity_query_size;
		}
	}

#ifdef USE_SSL
	/* 创建或附加到共享 SSL 状态缓冲区 */
	fc_size = mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots);
	BackendSslStatusBuffer = (PgBackendSSLStatus *)
		ShmemInitStruct("Backend SSL Status Buffer", fc_size, &fc_found);

	if (!fc_found)
	{
		PgBackendSSLStatus *fc_ptr;

		MemSet(BackendSslStatusBuffer, 0, fc_size);

		/* 初始化 st_sslstatus 指针。 */
		fc_ptr = BackendSslStatusBuffer;
		for (fc_i = 0; fc_i < NumBackendStatSlots; fc_i++)
		{
			BackendStatusArray[fc_i].st_sslstatus = fc_ptr;
			fc_ptr++;
		}
	}
#endif

#ifdef ENABLE_GSS
	/* 创建或附加到共享 GSSAPI 状态缓冲区 */
	fc_size = mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots);
	BackendGssStatusBuffer = (PgBackendGSSStatus *)
		ShmemInitStruct("Backend GSS Status Buffer", fc_size, &fc_found);

	if (!fc_found)
	{
		PgBackendGSSStatus *fc_ptr;

		MemSet(BackendGssStatusBuffer, 0, fc_size);

		/* 初始化 st_gssstatus 指针。 */
		fc_ptr = BackendGssStatusBuffer;
		for (fc_i = 0; fc_i < NumBackendStatSlots; fc_i++)
		{
			BackendStatusArray[fc_i].st_gssstatus = fc_ptr;
			fc_ptr++;
		}
	}
#endif
}

/*
 * 初始化 pgstats 后端活动状态，并设置我们的进程退出钩子。 从 InitPostgres 和 AuxiliaryProcessMain 调用。 对于辅助进程，MyBackendId 无效。 否则，必须设置 MyBackendId，但必须尚未启动任何事务（因为退出钩子必须在最后一个事务退出后运行）。
 *
 * 注意：MyDatabaseId 尚未设置；因此关闭钩子必须小心。
 */
void pgstat_beinit(void)
{
	/* 初始化 MyBEEntry */
	if (MyBackendId != InvalidBackendId)
	{
		Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
		MyBEEntry = &BackendStatusArray[MyBackendId - 1];
	}
	else
	{
		/* 必须是一个辅助进程 */
		Assert(MyAuxProcType != NotAnAuxProcess);

		/*
		 * 为辅助进程分配 MyBEEntry。 由于它没有 BackendId，因此插槽是基于辅助进程类型（MyAuxProcType）静态分配的。 后端使用从 0 到 MaxBackends（不包括）范围内索引的插槽，因此我们使用 MaxBackends + AuxProcType 作为辅助进程插槽的索引。
		 */
		MyBEEntry = &BackendStatusArray[MaxBackends + MyAuxProcType];
	}

	/* 设置一个进程退出钩子以进行清理 */
	on_shmem_exit(fc_pgstat_beshutdown_hook, 0);
}


/* ----------
 * pgstat_bestart() -
 *
 *	初始化该后端在 PgBackendStatus 数组中的条目。
 *	从 InitPostgres 调用。
 *
 *	除了辅助进程，MyBackendId、MyDatabaseId、会话用户 id 和 application_name 
 *	必须为一个后端设置（因此，这不能与 pgbestat_beinit 结合使用）。
 *	还要注意，如果这不是一个辅助进程，我们必须在事务中，因为我们可能需要
 *	对一些字符串进行编码转换。
 * ----------
 */
void pgstat_bestart(void)
{
	volatile PgBackendStatus *fc_vbeentry = MyBEEntry;
	PgBackendStatus fc_lbeentry;
#ifdef USE_SSL
	PgBackendSSLStatus fc_lsslstatus;
#endif
#ifdef ENABLE_GSS
	PgBackendGSSStatus fc_lgssstatus;
#endif

	/* pgstats 状态必须从 pgstat_beinit() 初始化 */
	Assert(fc_vbeentry != NULL);

	/*
	 * 为了最小化修改 PgBackendStatus 条目所花费的时间，并
	 * 避免在临界区内出现错误的风险，我们首先将
	 * 共享内存结构复制到一个局部变量，然后修改
	 * 局部变量中的数据，然后将局部变量复制回共享内存。
	 * 只有最后一步必须在临界区内进行。
	 *
	 * 我们从共享内存中复制的大多数数据将被直接覆盖，
	 * 但结构的大小并不算大，值得为只复制必要字段而
	 * 增加维护的麻烦。
	 */
	memcpy(&fc_lbeentry,
		   unvolatize(PgBackendStatus *, fc_vbeentry),
		   sizeof(PgBackendStatus));

	/* 这些结构可以每次都从零开始 */
#ifdef USE_SSL
	memset(&fc_lsslstatus, 0, sizeof(fc_lsslstatus));
#endif
#ifdef ENABLE_GSS
	memset(&fc_lgssstatus, 0, sizeof(fc_lgssstatus));
#endif

	/*
	 * 现在填写 lbeentry 的所有字段，除了那些为
	 * inline 数据的字符串。那些必须下面单独处理。
	 */
	fc_lbeentry.st_procpid = MyProcPid;
	fc_lbeentry.st_backendType = MyBackendType;
	fc_lbeentry.st_proc_start_timestamp = MyStartTimestamp;
	fc_lbeentry.st_activity_start_timestamp = 0;
	fc_lbeentry.st_state_start_timestamp = 0;
	fc_lbeentry.st_xact_start_timestamp = 0;
	fc_lbeentry.st_databaseid = MyDatabaseId;

	/* 我们有客户端后端、wal-sender 和 bgworker 进程的 userid */
	if (fc_lbeentry.st_backendType == B_BACKEND
		|| fc_lbeentry.st_backendType == B_WAL_SENDER
		|| fc_lbeentry.st_backendType == B_BG_WORKER)
		fc_lbeentry.st_userid = GetSessionUserId();
	else
		fc_lbeentry.st_userid = InvalidOid;

	/*
	 * 我们可能没有 MyProcPort（例如，如果这是自动
	 * 垃圾回收进程）。如果是这样，使用全零的客户端地址，
	 * 这在 pg_stat_get_backend_client_addr 和 pg_stat_get_backend_client_port 中
	 * 进行了特殊处理。
	 */
	if (MyProcPort)
		memcpy(&fc_lbeentry.st_clientaddr, &MyProcPort->raddr,
			   sizeof(fc_lbeentry.st_clientaddr));
	else
		MemSet(&fc_lbeentry.st_clientaddr, 0, sizeof(fc_lbeentry.st_clientaddr));

#ifdef USE_SSL
	if (MyProcPort && MyProcPort->ssl_in_use)
	{
		fc_lbeentry.st_ssl = true;
		fc_lsslstatus.ssl_bits = be_tls_get_cipher_bits(MyProcPort);
		strlcpy(fc_lsslstatus.ssl_version, be_tls_get_version(MyProcPort), NAMEDATALEN);
		strlcpy(fc_lsslstatus.ssl_cipher, be_tls_get_cipher(MyProcPort), NAMEDATALEN);
		be_tls_get_peer_subject_name(MyProcPort, fc_lsslstatus.ssl_client_dn, NAMEDATALEN);
		be_tls_get_peer_serial(MyProcPort, fc_lsslstatus.ssl_client_serial, NAMEDATALEN);
		be_tls_get_peer_issuer_name(MyProcPort, fc_lsslstatus.ssl_issuer_dn, NAMEDATALEN);
	}
	else
	{
		fc_lbeentry.st_ssl = false;
	}
#else
	fc_lbeentry.st_ssl = false;
#endif

#ifdef ENABLE_GSS
	if (MyProcPort && MyProcPort->gss != NULL)
	{
		const char *fc_princ = be_gssapi_get_princ(MyProcPort);

		fc_lbeentry.st_gss = true;
		fc_lgssstatus.gss_auth = be_gssapi_get_auth(MyProcPort);
		fc_lgssstatus.gss_enc = be_gssapi_get_enc(MyProcPort);
		if (fc_princ)
			strlcpy(fc_lgssstatus.gss_princ, fc_princ, NAMEDATALEN);
	}
	else
	{
		fc_lbeentry.st_gss = false;
	}
#else
	fc_lbeentry.st_gss = false;
#endif

	fc_lbeentry.st_state = STATE_UNDEFINED;
	fc_lbeentry.st_progress_command = PROGRESS_COMMAND_INVALID;
	fc_lbeentry.st_progress_command_target = InvalidOid;
	fc_lbeentry.st_query_id = UINT64CONST(0);

	/*
	 * 我们这里不将 st_progress_param 置零以节省周期；
	 * 在 st_progress_command 设置为除 PROGRESS_COMMAND_INVALID 以外的其他
	 * 值之前，没有人应检查它。
	 */

	/*
	 * 我们准备进入填写共享内存状态条目的临界区。
	 * 我们遵循在前后增加 st_changecount 的协议；
	 * 并确保它在之后是偶数。我们在这里使用一个易失性
	 * 指针以确保编译器不会试图优化。
	 */
	PGSTAT_BEGIN_WRITE_ACTIVITY(fc_vbeentry);

	/* 确保我们将相同的 st_changecount 复制回 */
	fc_lbeentry.st_changecount = fc_vbeentry->st_changecount;

	memcpy(unvolatize(PgBackendStatus *, fc_vbeentry),
		   &fc_lbeentry,
		   sizeof(PgBackendStatus));

	/*
	 * 我们可以使用 lbeentry 中的指针写入
	 * inline 字符串和结构；这节省了一些去易失化的麻烦。
	 */
	fc_lbeentry.st_appname[0] = '\0';
	if (MyProcPort && MyProcPort->remote_hostname)
		strlcpy(fc_lbeentry.st_clienthostname, MyProcPort->remote_hostname,
				NAMEDATALEN);
	else
		fc_lbeentry.st_clienthostname[0] = '\0';
	fc_lbeentry.st_activity_raw[0] = '\0';
	/* 还确保每个字符串区域的最后一个字节总是 0 */
	fc_lbeentry.st_appname[NAMEDATALEN - 1] = '\0';
	fc_lbeentry.st_clienthostname[NAMEDATALEN - 1] = '\0';
	fc_lbeentry.st_activity_raw[pgstat_track_activity_query_size - 1] = '\0';

#ifdef USE_SSL
	memcpy(fc_lbeentry.st_sslstatus, &fc_lsslstatus, sizeof(PgBackendSSLStatus));
#endif
#ifdef ENABLE_GSS
	memcpy(fc_lbeentry.st_gssstatus, &fc_lgssstatus, sizeof(PgBackendGSSStatus));
#endif

	PGSTAT_END_WRITE_ACTIVITY(fc_vbeentry);

	/* 将应用程序名称更新为当前 GUC 设置 */
	if (application_name)
		pgstat_report_appname(application_name);
}

/*
 * 清除我们在 PgBackendStatus 数组中的条目。
 */
static void fc_pgstat_beshutdown_hook(int fc_code, Datum fc_arg)
{
	volatile PgBackendStatus *fc_beentry = MyBEEntry;

	/*
	 * 清除我的状态条目，遵循在前后增加 st_changecount 的协议。
	 * 我们在这里使用一个易失性指针以确保编译器
	 * 不会试图优化。
	 */
	PGSTAT_BEGIN_WRITE_ACTIVITY(fc_beentry);

	fc_beentry->st_procpid = 0;	/* 标记为无效 */

	PGSTAT_END_WRITE_ACTIVITY(fc_beentry);

	/* 以便函数可以通过 MyBEEntry 检查 backend_status.c 是否正常 */
	MyBEEntry = NULL;
}

/*
 * 放弃在当前事务中收集的任何数据。任何后续
 * 请求将导致读取新的快照。
 *
 * 在事务提交或中止期间也会调用此函数以放弃
 * 不再需要的快照。
 */
void pgstat_clear_backend_activity_snapshot(void)
{
	/* 释放内存（如果有分配的话） */
	if (backendStatusSnapContext)
	{
		MemoryContextDelete(backendStatusSnapContext);
		backendStatusSnapContext = NULL;
	}

	/* 重置变量 */
	localBackendStatusTable = NULL;
	localNumBackends = 0;
}

static void fc_pgstat_setup_backend_status_context(void)
{
	if (!backendStatusSnapContext)
		backendStatusSnapContext = AllocSetContextCreate(TopMemoryContext,
														 "Backend Status Snapshot",
														 ALLOCSET_SMALL_SIZES);
}


/* ----------
 * pgstat_report_activity() -
 *
 *	从 tcop/postgres.c 调用以报告后台实际正在做什么
 *	（但请注意 cmd_str 在某些情况下可以为 NULL）。
 *
 * 所有状态条目的更新遵循在前后增加
 * st_changecount 的协议。我们在这里使用一个易失性指针以
 * 确保编译器不会试图进行优化。
 * ----------
 */
void pgstat_report_activity(BackendState fc_state, const char *fc_cmd_str)
{
	volatile PgBackendStatus *fc_beentry = MyBEEntry;
	TimestampTz fc_start_timestamp;
	TimestampTz fc_current_timestamp;
	int			fc_len = 0;

	TRACE_POSTGRESQL_STATEMENT_STATUS(cmd_str);

	if (!fc_beentry)
		return;

	if (!pgstat_track_activities)
	{
		if (fc_beentry->st_state != STATE_DISABLED)
		{
			volatile PGPROC *fc_proc = MyProc;

			/*
			 * track_activities 已禁用，但我们最后报告了一个
			 * 未禁用的状态。作为我们的最终更新，改变状态并
			 * 清除我们将不再更新的字段。
			 */
			PGSTAT_BEGIN_WRITE_ACTIVITY(fc_beentry);
			fc_beentry->st_state = STATE_DISABLED;
			fc_beentry->st_state_start_timestamp = 0;
			fc_beentry->st_activity_raw[0] = '\0';
			fc_beentry->st_activity_start_timestamp = 0;
			/* st_xact_start_timestamp 和 wait_event_info 也被禁用 */
			fc_beentry->st_xact_start_timestamp = 0;
			fc_beentry->st_query_id = UINT64CONST(0);
			fc_proc->wait_event_info = 0;
			PGSTAT_END_WRITE_ACTIVITY(fc_beentry);
		}
		return;
	}

	/*
	 * 为了最小化修改条目所花费的时间，并避免在关键区
	 * 域内发生错误的风险，先获取所有所需的数据。
	 */
	fc_start_timestamp = GetCurrentStatementStartTimestamp();
	if (fc_cmd_str != NULL)
	{
		/*
		 * 计算即将存储的字符串的长度，而不知道多字节
		 * 字符。出于速度考虑，这将在读取时纠正，
		 * 而不是每次写入时计算。
		 */
		fc_len = Min(strlen(fc_cmd_str), pgstat_track_activity_query_size - 1);
	}
	fc_current_timestamp = GetCurrentTimestamp();

	/*
	 * 如果状态已从“活动”或“事务中空闲”更改，
	 * 计算持续时间。
	 */
	if ((fc_beentry->st_state == STATE_RUNNING ||
		 fc_beentry->st_state == STATE_FASTPATH ||
		 fc_beentry->st_state == STATE_IDLEINTRANSACTION ||
		 fc_beentry->st_state == STATE_IDLEINTRANSACTION_ABORTED) &&
		fc_state != fc_beentry->st_state)
	{
		long		fc_secs;
		int			fc_usecs;

		TimestampDifference(fc_beentry->st_state_start_timestamp,
							fc_current_timestamp,
							&fc_secs, &fc_usecs);

		if (fc_beentry->st_state == STATE_RUNNING ||
			fc_beentry->st_state == STATE_FASTPATH)
			pgstat_count_conn_active_time((PgStat_Counter) fc_secs * 1000000 + fc_usecs);
		else
			pgstat_count_conn_txn_idle_time((PgStat_Counter) fc_secs * 1000000 + fc_usecs);
	}

	/*
	 * 现在更新状态条目
	 */
	PGSTAT_BEGIN_WRITE_ACTIVITY(fc_beentry);

	fc_beentry->st_state = fc_state;
	fc_beentry->st_state_start_timestamp = fc_current_timestamp;

	/*
	 * 如果开始了新查询，我们重置查询标识符，因为它仅会
	 * 在解析分析后知道，以避免报告上一个查询的
	 * 标识符。
	 */
	if (fc_state == STATE_RUNNING)
		fc_beentry->st_query_id = UINT64CONST(0);

	if (fc_cmd_str != NULL)
	{
		memcpy((char *) fc_beentry->st_activity_raw, fc_cmd_str, fc_len);
		fc_beentry->st_activity_raw[fc_len] = '\0';
		fc_beentry->st_activity_start_timestamp = fc_start_timestamp;
	}

	PGSTAT_END_WRITE_ACTIVITY(fc_beentry);
}

/* --------
 * pgstat_report_query_id() -
 *
 * 调用以更新顶层查询标识符。
 * --------
 */
void pgstat_report_query_id(uint64 fc_query_id, bool fc_force)
{
	volatile PgBackendStatus *fc_beentry = MyBEEntry;

	/*
	 * 如果 track_activities 被禁用，st_query_id 应该已经被
	 * 重置
	 */
	if (!fc_beentry || !pgstat_track_activities)
		return;

	/*
	 * 我们只报告顶层查询标识符。存储的 query_id 在
	 * 后台调用 pgstat_report_activity(STATE_RUNNING) 时重置，或者
	 * 使用 force 标志显式调用此函数时重置。如果
	 * 保存的查询标识符不为零，则意味着这不是一个顶层命令，
	 * 因此除非是显式调用重置标识符，否则忽略提供的标识符。
	 */
	if (fc_beentry->st_query_id != 0 && !fc_force)
		return;

	/*
	 * 更新我的状态条目，遵循在前后增加
	 * st_changecount 的协议。我们在这里使用一个易失性指针以
	 * 确保编译器不会试图进行优化。
	 */
	PGSTAT_BEGIN_WRITE_ACTIVITY(fc_beentry);
	fc_beentry->st_query_id = fc_query_id;
	PGSTAT_END_WRITE_ACTIVITY(fc_beentry);
}


/* ----------
 * pgstat_report_appname() -
 *
 *	调用以更新我们的应用程序名称。
 * ----------
 */
void pgstat_report_appname(const char *fc_appname)
{
	volatile PgBackendStatus *fc_beentry = MyBEEntry;
	int			fc_len;

	if (!fc_beentry)
		return;

	/* 如果 GUC 工作正常，这应该是不必要的，但还是要安全 */
	fc_len = pg_mbcliplen(fc_appname, strlen(fc_appname), NAMEDATALEN - 1);

	/*
	 * 更新我的状态条目，遵循在前后增加
	 * st_changecount 的协议。我们在这里使用一个易失性指针以
	 * 确保编译器不会试图进行优化。
	 */
	PGSTAT_BEGIN_WRITE_ACTIVITY(fc_beentry);

	memcpy((char *) fc_beentry->st_appname, fc_appname, fc_len);
	fc_beentry->st_appname[fc_len] = '\0';

	PGSTAT_END_WRITE_ACTIVITY(fc_beentry);
}

/*
 * 将当前事务的开始时间戳报告为指定值。
 * 零表示没有活动事务。
 */
void pgstat_report_xact_timestamp(TimestampTz fc_tstamp)
{
	volatile PgBackendStatus *fc_beentry = MyBEEntry;

	if (!pgstat_track_activities || !fc_beentry)
		return;

	/*
	 * 更新我的状态条目，遵循在前后增加
	 * st_changecount 的协议。我们在这里使用一个易失性指针以
	 * 确保编译器不会试图进行优化。
	 */
	PGSTAT_BEGIN_WRITE_ACTIVITY(fc_beentry);

	fc_beentry->st_xact_start_timestamp = fc_tstamp;

	PGSTAT_END_WRITE_ACTIVITY(fc_beentry);
}

/* ----------
 * pgstat_read_current_status() -
 *
 *	将 PgBackendStatus 数组的当前内容复制到本地内存中，
 *	如果在此事务中尚未完成。
 * ----------
 */
static void fc_pgstat_read_current_status(void)
{
	volatile PgBackendStatus *fc_beentry;
	LocalPgBackendStatus *fc_localtable;
	LocalPgBackendStatus *fc_localentry;
	char	   *fc_localappname,
			   *fc_localclienthostname,
			   *fc_localactivity;
#ifdef USE_SSL
	PgBackendSSLStatus *fc_localsslstatus;
#endif
#ifdef ENABLE_GSS
	PgBackendGSSStatus *fc_localgssstatus;
#endif
	int			fc_i;

	if (localBackendStatusTable)
		return;					/* 已经完成 */

	fc_pgstat_setup_backend_status_context();

	/*
	 * 为状态数据的本地副本分配存储空间。我们可以假设
	 * 这些请求都不会溢出 size_t，因为我们已经使用 mul_size 在
	 * shmem 设置过程中计算了相同的值。然而，考虑到
	 * pgstat_track_activity_query_size 和 max_connections 的
	 * 一些可能不合理的值，本地活动缓冲区可能会超过 1GB，
	 * 所以对于该项使用“巨大”分配。
	 */
	fc_localtable = (LocalPgBackendStatus *)
		MemoryContextAlloc(backendStatusSnapContext,
						   sizeof(LocalPgBackendStatus) * NumBackendStatSlots);
	fc_localappname = (char *)
		MemoryContextAlloc(backendStatusSnapContext,
						   NAMEDATALEN * NumBackendStatSlots);
	fc_localclienthostname = (char *)
		MemoryContextAlloc(backendStatusSnapContext,
						   NAMEDATALEN * NumBackendStatSlots);
	fc_localactivity = (char *)
		MemoryContextAllocHuge(backendStatusSnapContext,
							   (Size) pgstat_track_activity_query_size *
							   (Size) NumBackendStatSlots);
#ifdef USE_SSL
	fc_localsslstatus = (PgBackendSSLStatus *)
		MemoryContextAlloc(backendStatusSnapContext,
						   sizeof(PgBackendSSLStatus) * NumBackendStatSlots);
#endif
#ifdef ENABLE_GSS
	fc_localgssstatus = (PgBackendGSSStatus *)
		MemoryContextAlloc(backendStatusSnapContext,
						   sizeof(PgBackendGSSStatus) * NumBackendStatSlots);
#endif

	localNumBackends = 0;

	fc_beentry = BackendStatusArray;
	fc_localentry = fc_localtable;
	for (fc_i = 1; fc_i <= NumBackendStatSlots; fc_i++)
	{
		/*
		 * 遵循协议，如果 st_changecount 在我们复制条目时发生变化，
		 * 或者如果它是奇数，则重试。 （检查奇数是为了
		 * 处理我们能够完全复制条目的情况，而
		 * 源后端在增量步骤之间。）我们在这里使用一个 volatile
		 * 指针，以确保编译器不会试图做一些聪明的事情。
		 */
		for (;;)
		{
			int			fc_before_changecount;
			int			fc_after_changecount;

			pgstat_begin_read_activity(fc_beentry, fc_before_changecount);

			fc_localentry->backendStatus.st_procpid = fc_beentry->st_procpid;
			/* 如果条目未被使用，则跳过所有数据复制工作 */
			if (fc_localentry->backendStatus.st_procpid > 0)
			{
				memcpy(&fc_localentry->backendStatus, unvolatize(PgBackendStatus *, fc_beentry), sizeof(PgBackendStatus));

				/*
				 * 对于每个 PgBackendStatus 字段，如果是指针，复制
				 * 指向的数据，然后调整指针字段的本地副本，
				 * 使其指向数据的本地副本。
				 *
				 * strcpy 即使字符串被并发修改也很安全，
				 * 因为缓冲区的末尾始终有一个 \0。
				 */
				strcpy(fc_localappname, (char *) fc_beentry->st_appname);
				fc_localentry->backendStatus.st_appname = fc_localappname;
				strcpy(fc_localclienthostname, (char *) fc_beentry->st_clienthostname);
				fc_localentry->backendStatus.st_clienthostname = fc_localclienthostname;
				strcpy(fc_localactivity, (char *) fc_beentry->st_activity_raw);
				fc_localentry->backendStatus.st_activity_raw = fc_localactivity;
#ifdef USE_SSL
				if (fc_beentry->st_ssl)
				{
					memcpy(fc_localsslstatus, fc_beentry->st_sslstatus, sizeof(PgBackendSSLStatus));
					fc_localentry->backendStatus.st_sslstatus = fc_localsslstatus;
				}
#endif
#ifdef ENABLE_GSS
				if (fc_beentry->st_gss)
				{
					memcpy(fc_localgssstatus, fc_beentry->st_gssstatus, sizeof(PgBackendGSSStatus));
					fc_localentry->backendStatus.st_gssstatus = fc_localgssstatus;
				}
#endif
			}

			pgstat_end_read_activity(fc_beentry, fc_after_changecount);

			if (pgstat_read_activity_complete(fc_before_changecount,
											  fc_after_changecount))
				break;

			/* 确保我们可以在卡住时跳出循环... */
			CHECK_FOR_INTERRUPTS();
		}

		fc_beentry++;
		/* 仅有效条目被包括在本地数组中 */
		if (fc_localentry->backendStatus.st_procpid > 0)
		{
			BackendIdGetTransactionIds(fc_i,
									   &fc_localentry->backend_xid,
									   &fc_localentry->backend_xmin);

			fc_localentry++;
			fc_localappname += NAMEDATALEN;
			fc_localclienthostname += NAMEDATALEN;
			fc_localactivity += pgstat_track_activity_query_size;
#ifdef USE_SSL
			fc_localsslstatus++;
#endif
#ifdef ENABLE_GSS
			fc_localgssstatus++;
#endif
			localNumBackends++;
		}
	}

	/* 仅在完成有效表后设置指针 */
	localBackendStatusTable = fc_localtable;
}


/* ----------
 * pgstat_get_backend_current_activity() -
 *
 *	返回一个字符串，表示具有指定 PID 的后端的当前活动。
 *	这直接查看 BackendStatusArray，
 *	因此无论我们的事务状态数组快照的年龄如何，都会提供当前信息。
 *
 *	调用者有责任仅对那些状态在结果使用过程中预期保持稳定的后端调用此函数。
 *	当前唯一的用途是在死锁报告中，我们可以预期目标后端被锁定。
 *	（存在一些特殊情况可能会在我们查看时中止目标的等待，
 *	但最坏的后果是返回一个指向已被更改的字符串的指针，
 *	所以我们不会太担心。）
 *
 *	注意：特殊情况下返回的字符串与 pg_stat_get_backend_activity 匹配。
 * ----------
 */
const char * pgstat_get_backend_current_activity(int fc_pid, bool fc_checkUser)
{
	PgBackendStatus *fc_beentry;
	int			fc_i;

	fc_beentry = BackendStatusArray;
	for (fc_i = 1; fc_i <= MaxBackends; fc_i++)
	{
		/*
		 * 尽管我们期望目标后端的条目是稳定的，但这并不意味着其他人的条目也是。为了避免识别错误的后端，在我们检查与所需PID的匹配时，如果st_changecount在我们检查条目时发生变化，或如果它是奇数，我们必须遵循重试的协议。（这可能是多余的，因为获取或存储一个int几乎可以肯定是原子操作，但让我们小心点。）我们在这里使用一个volatile指针，以确保编译器不会试图搞花样。
		 */
		volatile PgBackendStatus *fc_vbeentry = fc_beentry;
		bool		fc_found;

		for (;;)
		{
			int			fc_before_changecount;
			int			fc_after_changecount;

			pgstat_begin_read_activity(fc_vbeentry, fc_before_changecount);

			fc_found = (fc_vbeentry->st_procpid == fc_pid);

			pgstat_end_read_activity(fc_vbeentry, fc_after_changecount);

			if (pgstat_read_activity_complete(fc_before_changecount,
											  fc_after_changecount))
				break;

			/* 确保我们可以在卡住时跳出循环... */
			CHECK_FOR_INTERRUPTS();
		}

		if (fc_found)
		{
			/* 现在可以安全使用非volatile指针 */
			if (fc_checkUser && !superuser() && fc_beentry->st_userid != GetUserId())
				return "<insufficient privilege>";
			else if (*(fc_beentry->st_activity_raw) == '\0')
				return "<command string not enabled>";
			else
			{
				/* 这会泄漏一点内存，但这似乎是可以接受的 */
				return pgstat_clip_activity(fc_beentry->st_activity_raw);
			}
		}

		fc_beentry++;
	}

	/* 如果我们到这里，调用者出错... */
	return "<backend information not available>";
}

/* ----------
 * pgstat_get_crashed_backend_activity() -
 *
 * 返回一个字符串，表示具有指定PID的后端的当前活动。像上面的函数，但读取共享内存，期望它可能已损坏。成功时，将字符串复制到“buffer”参数中并返回该指针。失败时，返回NULL。
 *
 * 该函数仅打算由postmaster使用，以报告使后端崩溃的查询。特别地，在访问BackendStatusArray时没有尝试遵循正确的并发协议。但这没关系，在最坏的情况下，我们将返回一个损坏的消息。我们还必须小心不要陷入ereport(ERROR)。
 * ----------
 */
const char * pgstat_get_crashed_backend_activity(int fc_pid, char *fc_buffer, int fc_buflen)
{
	volatile PgBackendStatus *fc_beentry;
	int			fc_i;

	fc_beentry = BackendStatusArray;

	/*
	 * 在共享内存设置完成之前，我们可能不应该到这里，但还是要安全一些。
	 */
	if (fc_beentry == NULL || BackendActivityBuffer == NULL)
		return NULL;

	for (fc_i = 1; fc_i <= MaxBackends; fc_i++)
	{
		if (fc_beentry->st_procpid == fc_pid)
		{
			/* 只读取指针一次，以便在验证后不会改变 */
			const char *fc_activity = fc_beentry->st_activity_raw;
			const char *fc_activity_last;

			/*
			 * 在验证活动字符串在BackendActivityBuffer内之前，我们不能访问它。为了确保整个字符串（包括结尾）都包含在缓冲区内，从缓冲区大小中减去一个活动长度。
			 */
			fc_activity_last = BackendActivityBuffer + BackendActivityBufferSize
				- pgstat_track_activity_query_size;

			if (fc_activity < BackendActivityBuffer ||
				fc_activity > fc_activity_last)
				return NULL;

			/* 如果没有可用字符串，就没有报告的意义 */
			if (fc_activity[0] == '\0')
				return NULL;

			/*
			 * 仅复制ASCII安全字符，以便在报告消息时不遇到编码问题；并确保不会超出内存末尾。由于仅报告ASCII字符，因此似乎没有必要执行多字节感知剪切。
			 */
			ascii_safe_strlcpy(fc_buffer, fc_activity,
							   Min(fc_buflen, pgstat_track_activity_query_size));

			return fc_buffer;
		}

		fc_beentry++;
	}

	/* PID未找到 */
	return NULL;
}

/* ----------
 * pgstat_get_my_query_id() -
 *
 * 返回当前后端的查询标识符。
 */
uint64 pgstat_get_my_query_id(void)
{
	if (!MyBEEntry)
		return 0;

	/*
	 * 在pgstat_begin_read_activity / pgstat_end_read_activity周围没有必要加锁，因为它仅从pg_stat_get_activity调用，该函数已经受到保护，或者从同一后端调用，这意味着没有并发写入。
	 */
	return MyBEEntry->st_query_id;
}


/* ----------
 * pgstat_fetch_stat_beentry() -
 *
 *	支持 SQL 可调用的 pgstat* 函数。返回
 *	当前活动条目的本地副本，针对一个后端。
 *
 *	注意：调用者负责检查用户是否被允许查看
 *	此信息（尤其是查询字符串）。
 * ----------
 */
PgBackendStatus *
pgstat_fetch_stat_beentry(int fc_beid)
{
	fc_pgstat_read_current_status();

	if (fc_beid < 1 || fc_beid > localNumBackends)
		return NULL;

	return &localBackendStatusTable[fc_beid - 1].backendStatus;
}


/* ----------
 * pgstat_fetch_stat_local_beentry() -
 *
 *	像 pgstat_fetch_stat_beentry() 但具有本地计算的附加信息（如
 *	xid 和 xmin 值的后端）
 *
 *	注意：调用者负责检查用户是否被允许查看
 *	此信息（尤其是查询字符串）。
 * ----------
 */
LocalPgBackendStatus *
pgstat_fetch_stat_local_beentry(int fc_beid)
{
	fc_pgstat_read_current_status();

	if (fc_beid < 1 || fc_beid > localNumBackends)
		return NULL;

	return &localBackendStatusTable[fc_beid - 1];
}


/* ----------
 * pgstat_fetch_stat_numbackends() -
 *
 *	支持 SQL 可调用的 pgstat* 函数。返回
 *	当前最大后端 ID。
 * ----------
 */
int pgstat_fetch_stat_numbackends(void)
{
	fc_pgstat_read_current_status();

	return localNumBackends;
}

/*
 * 将一个可能不安全地截断的活动字符串（参见
 * PgBackendStatus.st_activity_raw 的文档）转换为正确截断的
 * 字符串。
 *
 * 返回的字符串在调用者的内存上下文中分配，并可能被
 * 释放。
 */
char * pgstat_clip_activity(const char *fc_raw_activity)
{
	char	   *fc_activity;
	int			fc_rawlen;
	int			fc_cliplen;

	/*
	 * 一些调用者，如 pgstat_get_backend_current_activity()，不
	 * 确保缓冲区不会被同时修改。我们会尽量确保缓冲区始终以 NUL 字节结尾，但
	 * 仍需对字符串的长度保持警惕。在这些情况下，底层缓冲区保证为 
	 * pgstat_track_activity_query_size 大小。
	 */
	fc_activity = pnstrdup(fc_raw_activity, pgstat_track_activity_query_size - 1);

	/* 现在双重保证以 NUL 结尾 */
	fc_rawlen = strlen(fc_activity);

	/*
	 * 所有支持的服务器编码都可以从第一个字节确定
	 * 多字节字符的长度（客户端编码并非如此，请参见 GB18030）。
	 * 由于 st_activity 始终使用服务器编码存储，允许我们进行
	 * 多字节意识的截断，即使字符串之前在多字节
	 * 字符的中间被截断。
	 */
	fc_cliplen = pg_mbcliplen(fc_activity, fc_rawlen,
						   pgstat_track_activity_query_size - 1);

	fc_activity[fc_cliplen] = '\0';

	return fc_activity;
}
